Java 26-Day Course - Day 18: Lambda Expressions and Functional Interfaces

Day 18: Lambda Expressions and Functional Interfaces

Lambda expressions are a concise syntax for representing anonymous functions. Introduced in Java 8, they enable a functional programming style. They are used together with functional interfaces (interfaces with exactly one abstract method).

Lambda Expression Basic Syntax

Let’s walk through converting anonymous classes to lambdas.

import java.util.Arrays;
import java.util.List;

// Functional interface: exactly one abstract method
@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

@FunctionalInterface
interface Printer {
    void print(String message);
}

@FunctionalInterface
interface StringProcessor {
    String process(String input);
}

public class LambdaBasic {
    public static void main(String[] args) {
        // 1. Two parameters, expression body
        Calculator add = (a, b) -> a + b;
        Calculator multiply = (a, b) -> a * b;
        Calculator max = (a, b) -> Math.max(a, b);

        System.out.println("Addition: " + add.calculate(10, 20));
        System.out.println("Multiplication: " + multiply.calculate(5, 6));
        System.out.println("Max: " + max.calculate(15, 8));

        // 2. Single parameter (parentheses can be omitted)
        Printer printer = message -> System.out.println("Output: " + message);
        printer.print("Hello!");

        // 3. Block body (multiple lines)
        Calculator safeDivide = (a, b) -> {
            if (b == 0) {
                System.out.println("Cannot divide by zero.");
                return 0;
            }
            return a / b;
        };
        System.out.println("Division: " + safeDivide.calculate(10, 3));
        safeDivide.calculate(10, 0);

        // 4. String processing
        StringProcessor toUpper = input -> input.toUpperCase();
        StringProcessor addPrefix = input -> "[Java] " + input;
        System.out.println(toUpper.process("hello"));
        System.out.println(addPrefix.process("Lambda Expressions"));

        // 5. Using with list sorting
        List<String> names = Arrays.asList("Alice", "B", "Charlie", "Da");
        names.sort((a, b) -> a.length() - b.length());
        System.out.println("By length: " + names);
    }
}

Key Functional Interfaces

Core interfaces from the java.util.function package.

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class FunctionalInterfaces {
    public static void main(String[] args) {
        // Function<T, R>: input T -> output R
        Function<String, Integer> strLength = String::length;
        Function<Integer, String> intToStr = num -> "Number: " + num;
        System.out.println("Length: " + strLength.apply("Hello Java")); // 10
        System.out.println(intToStr.apply(42));

        // Function composition
        Function<String, String> toUpper = String::toUpperCase;
        Function<String, String> addBrackets = s -> "[" + s + "]";
        Function<String, String> combined = toUpper.andThen(addBrackets);
        System.out.println(combined.apply("hello")); // [HELLO]

        // Predicate<T>: input T -> boolean
        Predicate<Integer> isEven = n -> n % 2 == 0;
        Predicate<Integer> isPositive = n -> n > 0;
        Predicate<String> isNotEmpty = s -> s != null && !s.isEmpty();

        System.out.println("Is 4 even? " + isEven.test(4));       // true
        System.out.println("Empty string? " + isNotEmpty.test("")); // false

        // Combining Predicates
        Predicate<Integer> isEvenAndPositive = isEven.and(isPositive);
        System.out.println("4: " + isEvenAndPositive.test(4));   // true
        System.out.println("-2: " + isEvenAndPositive.test(-2)); // false

        // Consumer<T>: input T -> void (no return)
        Consumer<String> logger = msg -> System.out.println("[LOG] " + msg);
        Consumer<String> alert = msg -> System.out.println("[ALERT] " + msg);
        logger.accept("Server started");
        logger.andThen(alert).accept("Important event");

        // Supplier<T>: no input -> output T
        Supplier<Double> randomValue = Math::random;
        Supplier<String> greeting = () -> "Hello! Current time: " + System.currentTimeMillis();
        System.out.println("Random: " + randomValue.get());
        System.out.println(greeting.get());
    }
}

Method References

A way to express lambda expressions more concisely.

import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

public class MethodReference {
    static int add(int a, int b) {
        return a + b;
    }

    static boolean isAdult(int age) {
        return age >= 18;
    }

    public static void main(String[] args) {
        List<String> names = Arrays.asList("Java", "Python", "Go", "Rust");

        // Lambda vs method reference comparison

        // 1. Static method reference (ClassName::methodName)
        // Lambda: (a, b) -> Math.max(a, b)
        BiFunction<Integer, Integer, Integer> maxFunc = Math::max;
        System.out.println("Max: " + maxFunc.apply(10, 20));

        BiFunction<Integer, Integer, Integer> addFunc = MethodReference::add;
        System.out.println("Sum: " + addFunc.apply(5, 3));

        // 2. Instance method reference (instance::methodName)
        String prefix = "Hello, ";
        Function<String, String> greeter = prefix::concat;
        System.out.println(greeter.apply("Java!")); // Hello, Java!

        // 3. Arbitrary object instance method reference (ClassName::methodName)
        // Lambda: (s) -> s.toUpperCase()
        names.stream()
             .map(String::toUpperCase) // Calls toUpperCase on each String instance
             .forEach(System.out::println);

        // 4. Constructor reference (ClassName::new)
        Function<String, StringBuilder> sbCreator = StringBuilder::new;
        StringBuilder sb = sbCreator.apply("Constructor reference!");
        System.out.println(sb);

        // Using method reference in sorting
        List<String> fruits = Arrays.asList("Banana", "Apple", "Grape", "Strawberry");
        fruits.sort(String::compareTo);
        System.out.println("Sorted: " + fruits);
    }
}

Practical Pattern: Strategy Pattern and Functional Programming

import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

public class FunctionalPatterns {
    // Filtering utility
    static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T item : list) {
            if (predicate.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

    // Transformation utility
    static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
        List<R> result = new ArrayList<>();
        for (T item : list) {
            result.add(mapper.apply(item));
        }
        return result;
    }

    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Inject strategies as lambdas
        List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
        List<Integer> bigNumbers = filter(numbers, n -> n > 5);
        List<String> strings = map(numbers, n -> "Number: " + n);
        List<Integer> doubled = map(numbers, n -> n * 2);

        System.out.println("Even: " + evenNumbers);
        System.out.println("Greater than 5: " + bigNumbers);
        System.out.println("Strings: " + strings);
        System.out.println("Doubled: " + doubled);

        // Function pipeline
        Function<Integer, Integer> pipeline = ((Function<Integer, Integer>) (n -> n * 2))
            .andThen(n -> n + 10)
            .andThen(n -> n * n);

        System.out.println("Pipeline(5): " + pipeline.apply(5));
        // 5 -> 10 -> 20 -> 400
    }
}

Today’s Exercises

  1. String Transformer: Create several transformation functions of type Function<String, String> (trim, uppercase, lowercase, reverse) and chain them with andThen to transform the string ” Hello World ” in various ways.

  2. Filter Chain: Combine Predicate<Integer> predicates (and, or, negate) to filter numbers from an integer list that are “positive and a multiple of 3” or “even and greater than 10.”

  3. Simple Event System: Create an EventBus class using Map<String, List<Consumer<String>>> to register and emit handlers by event name. It should support on("click", handler) and emit("click", data) usage.

Was this article helpful?